<?php class Caturday_A {
	protected $Fetcher = NULL;
	protected $Data = array ();
	protected $Stack = array ();

	protected $RE_START       = '(^ \\s* HAI! \\s* $)x';
	protected $RE_FINISH      = '(^ \\s* KTHXBAI \\s* $)x';
	protected $RE_ENTITY      = '[_a-zA-Z] [0-9_a-zA-Z]*';
	protected $RE_TEMPLATE    = '[-_a-zA-Z0-9]+';
	protected $RE_BLOCKEND    = '(^ \\s* KTHX \\s* $)x';
	protected $RE_ELSE        = '(^ \\s* OH \\s NOES! \\s* $)x';
	protected $RE_COMPOUND    = '';
	protected $RE_INTERPOLATE = '';
	protected $RE_INCLUDE     = '';
	protected $RE_FLOWOP      = '';

	protected function Fetcher ($Template) {return (file_exists ($FName = "$Template.cat")) ? rtrim (file_get_contents ($FName)) : FALSE;}

	protected function StackInit () {$this->Stack = array ();}
	protected function StackSize () {return (count ($this->Stack));}
	protected function StackPush (array $Elem) {array_push ($this->Stack, $Elem);}
	protected function StackPushBranch ($Line, $Value, $ElsePart = FALSE) {
		$this->StackPush (array ('Type' => 'B', 'Line' => $Line, 'Value' => $Value, 'Else' => $ElsePart));
	}
	protected function StackIsBranch ($Offset = 0) {
		if (($Size = $this->StackSize ()) >= $Offset + 1) return ('B' === $this->Stack [$Size - $Offset - 1]['Type']);
	}
	protected function StackPushIterator ($Line, array $Value, $Current = 0, $ElsePart = FALSE) {
		$this->StackPush (array ('Type' => 'I', 'Line' => $Line, 'Value' => $Value, 'Cur' => $Current, 'Else' => $ElsePart));
	}
	protected function StackIsIterator ($Offset = 0) {
		if (($Size = $this->StackSize ()) >= $Offset + 1) return ('I' === $this->Stack [$Size - $Offset - 1]['Type']);
	}
	protected function StackType ($Offset = 0) {return (($Size = $this->StackSize ()) >= $Offset + 1) ? $this->Stack [$Size - $Offset - 1]['Type'] : FALSE;}
	protected function StackPop () {return ($this->StackSize ()) ? array_pop ($this->Stack) : FALSE;}
	protected function StackLine () {return ($Size = $this->StackSize ()) ? $this->Stack [$Size - 1]['Line'] : FALSE;}
	protected function StackElsePart () {return (($Size = $this->StackSize ()) and $this->Stack [$Size - 1]['Else']);}
	protected function StackElsePartTurnOn () {return ($Size = $this->StackSize ()) ? $this->Stack [$Size - 1]['Else'] = TRUE : FALSE;}
	protected function StackHasCurrent ($Offset = 0) {
		if (($Size = $this->StackSize ()) < $Offset + 1) return (FALSE);
		if ($this->StackIsIterator ($Offset) and $this->Stack [$Size - $Offset - 1] ['Cur'] >= count ($this->Stack [$Size - $Offset - 1] ['Value'])) return (FALSE);
		return (TRUE);
	}
	protected function StackCurrent ($Offset = 0) {
		if (! $this->StackHasCurrent ($Offset)) return (NULL);
		$Size = $this->StackSize ();
		switch ($this->StackType ($Offset)) {
			case 'B': return ($this->Stack [$Size - $Offset - 1]['Value']);
			case 'I': return ($this->Stack [$Size - $Offset - 1]['Value'][$this->Stack [$Size - $Offset - 1]['Cur']]);
		}
	}
	protected function StackCurrentNotEmpty ($Offset = 0) {
		if (! $this->StackHasCurrent ($Offset)) return (FALSE);
		if (is_object ($Current = $this->StackCurrent ($Offset))
		    or is_numeric ($Current)
		    or ( is_array ($Current) and count ($Current) )
		    or ( is_string ($Current) and strlen ($Current) )
		   ) return (TRUE);
		return (FALSE);
	}
	protected function StackIterate () {
		if (! $this->StackIsIterator ()) return (FALSE);
		$this->Stack [($Size = $this->StackSize ()) - 1]['Cur'] += 1;
		if ($this->Stack [$Size - 1]['Cur'] >= count ($this->Stack [$Size - 1]['Value'])) {
			$this->Stack [$Size - 1]['Cur'] = count ($this->Stack [$Size - 1]['Value']);
			return (FALSE);
		}
		return (TRUE);
	}

	protected function Lookup ($Compound) {
		$Result = NULL;
		$Compound = explode ('.', $Compound);
		switch ($First = array_shift ($Compound)) {
			case 'IT': case 'THAT':
				$Offset = ('THAT' === $First) ? 1 : 0;
				$Result = $this->StackCurrent ($Offset);
			break;
			default: isset ($this->Data [$First]) and $Result = $this->Data [$First];
		}
		foreach ($Compound as $Part) {
			if (is_array ($Result) and isset ($Result [$Part])) {$Result = $Result [$Part]; continue;}
			if (is_object ($Result) and property_exists ($Result, $Part)) {$Result = $Result->$Part; continue;}
			break;
		}
		return ($Result);
	}
	protected function Interpolate ($Matches) {
		if (count ($Matches) < 3) return (FALSE);
		list ($Original, $Command, $Compound) = array_values ($Matches);
		return ($this->Lookup ($Compound));
	}

	protected function _Interpret ($Template) {
		if (! $Template = call_user_func ($this->Fetcher, $Template)) return (FALSE);
		$Template = explode ("\n", $Template);
		for ($Current = 0, $Result = $Matches = array (), $ParserEnabled = FALSE; $Current < count ($Template); $Current += 1) {
			if (preg_match ($this->RE_START, $Line = $Template [$Current])) {$ParserEnabled = TRUE; continue;}
			if ($ParserEnabled) {
				if (preg_match ($this->RE_FINISH, $Line)) {$ParserEnabled = FALSE; continue;}
				$Line = preg_replace_callback ($this->RE_INTERPOLATE, array ($this, 'Interpolate'), $Line);
				if (preg_match ($this->RE_ELSE, $Line)) {$this->StackElsePartTurnOn (); continue;}
				if (preg_match ($this->RE_BLOCKEND, $Line) and $this->StackSize ()) {
					if ($this->StackIsIterator () and $this->StackHasCurrent () and $this->StackIterate ()) {
						$Current = $this->StackLine ();
					} else $this->StackPop ();
					continue;
				}
				if (preg_match ($this->RE_INCLUDE, $Line, $Matches) and isset ($Matches [1])) {
					$Line = call_user_func (array ($this, __FUNCTION__), $Matches [1]);
				}
				if (preg_match ($this->RE_FLOWOP, $Line, $Matches) and count ($Matches) > 2) {
					list ($Original, $Command, $Compound) = array_values ($Matches);
					$Compound = $this->Lookup ($Compound);
					if ($Command) {
						is_array ($Compound) or $Compound = array ($Compound);
						$this->StackPushIterator ($Current, $Compound);
					} else $this->StackPushBranch ($Current, $Compound);
					continue;
				}
			}
			if (! $this->StackSize () or ($this->StackCurrentNotEmpty () xor $this->StackElsePart ())) array_push ($Result, $Line);
		}
		return (implode ("\n", $Result));
	}

// ---------------------------------------------------------------
// PUBLIC INTERFACE
//
	public function __construct () {
		$this->RE_COMPOUND    = "{$this->RE_ENTITY} (?: \\. {$this->RE_ENTITY} )*";
		$this->RE_INTERPOLATE = "( ` ( [?!]? ) ( {$this->RE_COMPOUND} ) ` )x";
		$this->RE_INCLUDE     = "(^ \\s* IM \\s IN \\s UR \\s TMPLAET, \\s REEDIN \\s UR \\s ( {$this->RE_TEMPLATE} ) \\s* $)x";
		$this->RE_FLOWOP      = "(^ \\s* I \\s CAN \\s HAZ (?: \\s ( ICH ) )? \\s ( {$this->RE_COMPOUND} ) \\? \\s* $)x";
		$this->FetcherAssign (array ($this, 'Fetcher'));
	}
	public function FetcherAssign ($Fetcher) {
		if (! is_callable ($Fetcher)) return (FALSE);
		$this->Fetcher = $Fetcher;
		return (TRUE);
	}
	public function DatasourceRegister ($Name, &$Datasource) {
		if (! is_scalar ($Name)) return (FALSE);
		$this->Data [$Name] = &$Datasource;
		return (TRUE);
	}
	public function DatasourceUnregister ($Name) {
		if (! is_scalar ($Name) or ! isset ($this->Data [$Name])) return (FALSE);
		unset ($this->Data [$Name]);
		return (TRUE);
	}
	public function Interpret ($Template) {
		$this->StackInit ();
		return ($this->_Interpret ($Template));
	}
} ?>